本篇會介紹如何將函式當成引數,傳遞給其他函式;函式也可以傳回函式,甚至可以拿函式賦值給變數(如閉包);除此之外會說明defer,延後函式的執行時機。
自訂函式型別用途範例 :
package main
import "fmt"
type calc func(int, int) string // 自訂函式型別
func main() {
calculator(add, 5, 6)
}
func add(i, j int) string {
result := i + j
return fmt.Sprintf("%d + %d = %d", i, j, result)
}
//接收自訂函式型別參數 f
//效果等同寫 f func(int, int) string
func calculator(f calc, i, j int) {
fmt.Println(f(i, j))//呼叫傳入函式
}
結果:
由上可以看到,add(i, j int) 以及 func(int, int)的signature是一樣的,因此add()可被視為calc型別,而函式calculator接受一個calc 型別參數,因此我們可以將add傳給他**(只填入名稱,不帶小括弧)**
我們仔細說明上述範例程式:
type calc func(int, int) string // 自訂函式型別
表示calc是一個函是型別,它的特徵包括兩個int參數、一個string回傳值,只要任何函是符合此特徵,他就是calc。
func add(i, j) string
如上述,他符合calc特徵,所以他是calc型別。
func calculator(f calc, j int)
calculator函式的參數f 為 calc 型別,而add()如上所說符合calc 型態,因此可以作為傳入f的引數。
範例二 :
package main
import "fmt"
type salaryfunc func(int, int) int // 自訂函式型別
func main() {
devSalary := salary(50, 2080, developerSalary)
bossSalary := salary(15000, 25000, managerSalary)
fmt.Printf("經理薪資 : %d\n", bossSalary)
fmt.Printf("開發者薪資 : %d\n", devSalary)
}
func salary(x, y int, f salaryfunc) int {
pay := f(x, y)
return pay
}
func managerSalary(bossSalary, bonus int) int {
return bossSalary + bonus
}
func developerSalary(hourlyRate, hoursWorked int) int {
return hourlyRate * hoursWorked
}
以這個範例來說,我們以salary()這個函式簡化了程式碼,將需要額外計算的developerSalary新建一個函式,且特徵符合salaryFunc,就可以當作引數傳入,這樣就不用去調整salary()本身。
我們不只能在函是參數使用自訂函式型別,也可以將它當成傳回值型別,如以下範例:
package main
import "fmt"
type salaryfunc func(int, int) int // 自訂函式型別
func main() {
add := calculator("+")
subtract := calculator("-")
fmt.Println(add(5, 6))
fmt.Println(subtract(10, 5))
fmt.Println("add() 型別: %T\n", add)
fmt.Println("subtract() 型別: %T\n", subtract)
}
func calculator(operator string) func(int, int) int {
//根據使用者的引數傳回對應的函式
switch operator {
case "+":
return func(i, j int) int {
return i + j
}
case "-":
return func(i, j int) int {
return i - j
}
}
return nil
}
結果 :
在本小節將介紹defer,看看如何改變函式執行的時機點。
defer敘述能延後函式執行時機,使該函式等到父函式結束(跑完所有程式碼或執行return)的前一刻,才會被執行。
簡單說,在呼叫某個函式時加上defer,他不會當場執行,而是變成它所在的父函式裡最後一個執行的東西。
來看例子吧 :
package main
import "fmt"
func main() {
defer done()
fmt.Println("main() 開始")
fmt.Println("main() 結束")
}
func done() {
fmt.Println("換我結束!")
}
結果:
這樣就比較好了解了八,加了defer就會延後done()執行時機。
defer 的使用時機
defer 通常是用來善後的,包括像是釋放資源、關閉以開啟的的檔案、關閉DB連線、移除程式先前建立的設定/站存檔,除此之外,defer還可以用來從程式的錯誤狀況復原,下一個主題就會談到這部分。
同理,defer 不僅具名函式可用而且匿名函式也可以!!
func main() {
defer func() {
fmt.Println("換我結束")
}()
fmt.Println("main()開始")
fmt.Println("main()結束")
}
我們也可以在同一個函式內使用多個defer敘述來延後多個函式,但這個函式被延後的順序,是遵循先進後出FILO(First In Last Out),先進後出的概念簡而言之就是先進去的會後出來(廢話),可以想像成疊盤子,當盤子從第一個越疊越高,當我們要取會第一個盤子時,一定是從最高的開始取,所以第一個盤子進去,會最後一個出來。
來看個例子 :
package main
import "fmt"
func main() {
defer func() {
fmt.Println("我是第1個宣告出來的")
}()
defer func() {
fmt.Println("我是第2個宣告出來的")
}()
defer func() {
fmt.Println("我是第3個宣告出來的")
}()
defer func() {
fmt.Println("我是第4個宣告出來的")
}()
defer func() {
fmt.Println("我是第5個宣告出來的")
}()
f1 := func() {
fmt.Println("f1結束")
}
f2 := func() {
fmt.Println("f2結束")
}
f1()
f2()
fmt.Println("main() 結束!")
}
結果如下 :
使用defer敘述時請務必審慎,其中一個必須考慮到的是,如果defer函式有使用到外部變數,它執行時會發生什麼結果。
package main
import "fmt"
func main() {
age := 25
name := "John"
defer personAge(name, age)
age *= 2
fmt.Println("年齡加倍:")
personAge(name, age)
}
func personAge (name string, i int) {
fmt.Printf("%s 是 %d 歲\n", name, i)
}
輸出結果 :
對此範例,我們可以得知,這個defer只會取得變數再傳遞的那一刻當下的值,就算是變數在之後有所變動,等到defer函式實際執行時,他看到的變數值也不會反映外圍函式中的變動。
以上幾張就是Go語言函式的整理與介紹,大概可以知道如何定義和呼叫函式、各種函式類型、閉包以及本篇的defer延遲函式,接下來將進入到Go語言中error值及錯誤型別。